/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtDbSession.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"

/* Create a new WtDbSession */

Tcl_Obj *WtNewDbSessionObj()
{
  Tcl_Obj *obj = Tcl_NewObj();
  WtDbSession *ds = (WtDbSession *)ckalloc(sizeof(WtDbSession));

  ds->id = NULL;

  ds->isAutoId = 0;
  ds->isOpen = 0;
  ds->isFetched = 0;

  ds->items = WtNewTableObj();
  Tcl_IncrRefCount(ds->items);

  ds->checkpoint = NULL;

  ds->isModified = 0;

  ds->deletions = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(ds->deletions);

  ds->insertions = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(ds->insertions);

  ds->isCleared = 0;
  ds->commitStatus = WT_STATUS_NONE;

  ds->accessTime = -1;
  ds->syncedAccessTime = 0;
  ds->accessTimeInterval = 300;

#ifdef WIN32
  ds->dbHandler = WtNewString("::wt::session::dbSession::odbcSession::sqlServerSession::");
#else
  ds->dbHandler = WtNewString(NULL);
#endif /* WIN32 */

  Tcl_IncrRefCount(ds->dbHandler);

  ds->dbHandlerData = WtNewString(NULL);
  Tcl_IncrRefCount(ds->dbHandlerData);

  ds->dbHandlerLoaded = 0;
  ds->dbSessionOpened = 0;

  obj->internalRep.twoPtrValue.ptr1 = ds;
  obj->internalRep.twoPtrValue.ptr2 = NULL;
  obj->typePtr = &WtDbSessionType;
  Tcl_InvalidateStringRep(obj);

  return obj;
}

/* Get the internal rep */

WtDbSession *WtDbSessionRep(Tcl_Obj *obj)
{
  return (WtDbSession *)obj->internalRep.twoPtrValue.ptr1;
}

/* Copy a WtDbSession */

static void WtDbSessionDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtDbSession *srcRep = WtDbSessionRep(src);
  WtDbSession *dstRep = (WtDbSession *)ckalloc(sizeof(WtDbSession));

  if (srcRep->id) {
    dstRep->id = Tcl_DuplicateObj(srcRep->id);
    Tcl_IncrRefCount(dstRep->id);
  } else {
    dstRep->id = NULL;
  }

  dstRep->isAutoId = srcRep->isAutoId;
  dstRep->isOpen = srcRep->isOpen;
  dstRep->isFetched = srcRep->isFetched;

  dstRep->items = Tcl_DuplicateObj(srcRep->items);
  Tcl_IncrRefCount(dstRep->items);

  if (srcRep->checkpoint) {
    dstRep->checkpoint = Tcl_DuplicateObj(srcRep->checkpoint);
    Tcl_IncrRefCount(dstRep->checkpoint);
  } else {
    dstRep->checkpoint = srcRep->checkpoint;
  }

  dstRep->isModified = srcRep->isModified;

  dstRep->deletions = Tcl_DuplicateObj(srcRep->deletions);
  Tcl_IncrRefCount(dstRep->deletions);

  dstRep->insertions = Tcl_DuplicateObj(srcRep->insertions);
  Tcl_IncrRefCount(dstRep->insertions);

  dstRep->isCleared = srcRep->isCleared;
  dstRep->commitStatus = srcRep->commitStatus;

  dstRep->accessTime = srcRep->accessTime;
  dstRep->syncedAccessTime = srcRep->syncedAccessTime;
  dstRep->accessTimeInterval = srcRep->accessTimeInterval;

  dstRep->dbHandler = Tcl_DuplicateObj(srcRep->dbHandler);
  Tcl_IncrRefCount(dstRep->dbHandler);

  dstRep->dbHandlerData = Tcl_DuplicateObj(srcRep->dbHandlerData);
  Tcl_IncrRefCount(dstRep->dbHandlerData);

  dstRep->dbHandlerLoaded = srcRep->dbHandlerLoaded;
  dstRep->dbSessionOpened = srcRep->dbSessionOpened;

  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->internalRep.twoPtrValue.ptr2 = NULL;
  dst->typePtr = &WtDbSessionType;
}

/* Update the string rep */

static void WtDbSessionUpdateString(Tcl_Obj *obj)
{
  WtUpdateProtectedObjString(obj);
}

/* Convert an obj to a WtDbSession */

static int WtDbSessionSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetProtectedObjFromAny(interp, obj, &WtDbSessionType);
}

/* Free a WtDbSession */

static void WtDbSessionFree(Tcl_Obj *obj)
{
  WtDbSession *ds;

  ds = WtDbSessionRep(obj);

  if (ds->id) {
    Tcl_DecrRefCount(ds->id);
  }
  Tcl_DecrRefCount(ds->items);
  Tcl_DecrRefCount(ds->deletions);
  Tcl_DecrRefCount(ds->insertions);
  Tcl_DecrRefCount(ds->dbHandler);
  Tcl_DecrRefCount(ds->dbHandlerData);

  ckfree((char *)(ds));
}

/* DB session type */

struct Tcl_ObjType WtDbSessionType =
{
  "dbSession",
  WtDbSessionFree,
  WtDbSessionDup,
  WtDbSessionUpdateString,
  WtDbSessionSetFromAny
};

/* Initialize the DB session ID */

int WtInitDbSessionId(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;
  WtSession *s;

  if (ds->id) {
    ok = 1;
  } else {
    s = WtGetAssocSession(interp);
    if (ds->id = WtGetInitialSessionId(s, &ds->isAutoId, interp)) {
      Tcl_IncrRefCount(ds->id);
      ok = 1;
    }
  }

  return ok;
}

/* Get the session ID */

Tcl_Obj *WtGetDbSessionId(WtDbSession *ds, Tcl_Interp *interp)
{
  return WtInitDbSessionId(ds, interp) ? ds->id : NULL;
}

/* Set the session ID. Closes the current session. */

int WtSetDbSessionId(WtDbSession *ds, Tcl_Obj *id, Tcl_Interp *interp)
{
  int ok = 0;

  if (!Tcl_GetCharLength(id)) {
    Tcl_AppendResult(interp, "Session ID string is invalid.", NULL);
  } else if (ds->id && !strcmp(WtToString(ds->id), WtToString(id))) {
    ok = 1;
  } else if (!ds->isOpen || WtCloseDbSession(ds, interp, 1)) {
    if (ds->id) {
      Tcl_DecrRefCount(ds->id);
    }
    ds->id = id;
    Tcl_IncrRefCount(ds->id);
    ds->isAutoId = 0;
    ok = 1;
  }

  return ok;
}

/* Open the session */

int WtOpenDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;

  if (ds->isOpen) {
    ok = 1;
  } else if (WtInitDbSessionId(ds, interp)) {
    ds->isOpen = 1;
    ok = 1;
  }

  return ok;
}

/* Close the session */

int WtCloseDbSession(WtDbSession *ds, Tcl_Interp *interp, int doCommit)
{
  int ok = 0;

  if (!ds->isOpen) {
    ok = 1;
  } else if (!WtCommitIsPending(ds) ||
      (doCommit && WtCommitDbSession(ds, interp))) {
    if (!ds->dbSessionOpened ||
        WtCloseDbSessionResource(ds, interp)) {
      ds->isOpen = 0;
      ds->accessTime = -1;
      ds->syncedAccessTime = 0;
      ds->commitStatus = WT_STATUS_NONE;
      WtClearDbSessionCache(ds, interp);
      WtClearDbSessionUpdates(ds, interp);
      ok = 1;
    }
  }

  return ok;
}

/* Open the session resource */

int WtOpenDbSessionResource(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;

  if (ds->dbSessionOpened) {
    ok = 1;
  } else if (ds->isOpen || WtOpenDbSession(ds, interp)) {
    if (WtDbSessionHandlerStr(ds, ds->dbHandler,
        "open", interp, 1, &ds->id) == TCL_OK) {
      ds->dbSessionOpened = 1;
      ok = 1;
    }
  }

  return ok;
}

/* Close the session resource */

int WtCloseDbSessionResource(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;

  if (!ds->dbSessionOpened) {
    ok = 1;
  } else if (WtDbSessionHandlerStr(ds, ds->dbHandler,
      "close", interp, 1, &ds->id) == TCL_OK) {
    ds->dbSessionOpened = 0;
    ok = 1;
  }

  return ok;
}

/* Set the handler. Unloads the current handler. */

int WtSetDbSessionHandler(WtDbSession *ds, Tcl_Obj *handler, Tcl_Interp *interp)
{
  int ok = 0;

  if (!Tcl_GetCharLength(handler)) {
    Tcl_AppendResult(interp, "Database handler string is invalid.", NULL);
  } else if (!strcmp(WtToString(ds->dbHandler), WtToString(handler))) {
    ok = 1;
  } else if (!ds->dbHandlerLoaded || WtUnloadDbSessionStore(ds, interp, 1)) {
    Tcl_DecrRefCount(ds->dbHandler);
    ds->dbHandler = handler;
    Tcl_IncrRefCount(ds->dbHandler);
    ok = 1;
  }

  return ok;
}

/* Load the handler */

int WtLoadDbSessionStore(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;

  if (ds->dbHandlerLoaded) {
    ok = 1;
  } else if (!Tcl_GetCharLength(ds->dbHandler)) {
    Tcl_AppendResult(interp, "Database handler string is invalid.", NULL);
  } else if (WtDbSessionHandlerStr(ds, ds->dbHandler,
      "load", interp, 0, NULL) == TCL_OK) {
    ds->dbHandlerLoaded = 1;
    ok = 1;
  }

  return ok;
}

/* Unload the handler */

int WtUnloadDbSessionStore(WtDbSession *ds, Tcl_Interp *interp, int doClose)
{
  int ok = 1;
  Tcl_Obj *err, *msg;

  msg = WtNewString(NULL);
  Tcl_IncrRefCount(msg);

  if (ds->dbHandlerLoaded) {
    if (doClose && ds->isOpen && !WtCloseDbSession(ds, interp, 1)) {
      err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
      Tcl_AppendObjToObj(msg, err);
      ok = 0;
    }

    if (WtDbSessionHandlerStr(ds, ds->dbHandler,
        "unload", interp, 0, NULL) != TCL_OK) {
      err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
      if (Tcl_GetCharLength(msg)) {
        Tcl_AppendToObj(msg, "\n    and\n", -1);
      }
      Tcl_AppendObjToObj(msg, err);
      ok = 0;
    } else {
      ds->dbHandlerLoaded = 0;
    }
  }

  if (!ok) {
    Tcl_ResetResult(interp);
    Tcl_AddObjErrorInfo(interp, WtToString(msg), -1);
  }

  Tcl_DecrRefCount(msg);

  return ok;
}

/* Get all session entries */

int WtFetchDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0;
  Tcl_Obj *obj, *key, *val, *items, *deletions, *insertions;
  Tcl_HashTable *hashTable;
  Tcl_HashSearch search;
  Tcl_HashEntry *ent;

  if (ds->isFetched) {
    if (WtConvertToTable(ds->items, interp)) {
      ok = 1;
    }
  } else if (WtOpenDbSessionResource(ds, interp)) {
    if (WtDbSessionHandlerStr(ds, ds->dbHandler, "fetch", interp, 1,
        &ds->id) == TCL_OK) {
      if (!(obj = Tcl_GetObjResult(interp))) {
        Tcl_AppendResult(interp,
          "Failed to fetch session items using database handler \"",
          WtSafeStr(WtToString(ds->dbHandler)), "\".", NULL);
      } else if (WtConvertToTable(obj, interp)) {
        Tcl_IncrRefCount(obj);
        if (ds->items) {
          Tcl_DecrRefCount(ds->items);
        }
        ds->items = obj;

        if (ds->checkpoint) {
          Tcl_DecrRefCount(ds->checkpoint);
          ds->checkpoint = NULL;
        }

        if ((items = WtGetDbSessionItems(ds, interp)) &&
            (deletions = WtGetDbSessionDeletions(ds, interp)) &&
            (insertions = WtGetDbSessionInsertions(ds, interp))) {
          hashTable = WtGetTableMap(deletions);
          ent = Tcl_FirstHashEntry(hashTable, &search);
          while (ent) {
            key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
            WtTableRemove(items, key);
            ent = Tcl_NextHashEntry(&search);
          }

          ok = 1;
          hashTable = WtGetTableMap(insertions);
          ent = Tcl_FirstHashEntry(hashTable, &search);

          while (ent) {
            key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
            val = (Tcl_Obj *)Tcl_GetHashValue(ent);
            WtTableSet(items, key, val);
            ent = Tcl_NextHashEntry(&search);
          }

          if (ok) {
            ds->checkpoint = Tcl_DuplicateObj(items);
            Tcl_IncrRefCount(ds->checkpoint);
            ds->isFetched = 1;
          }
        }
      }
    }
  }

  return ok;
}

/* Get the items */

Tcl_Obj *WtGetDbSessionItems(WtDbSession *ds, Tcl_Interp *interp)
{
  if (WtOwnTable(&ds->items, interp)) {
    return ds->items;
  }
  return NULL;
}

/* Get the insertions */

Tcl_Obj *WtGetDbSessionInsertions(WtDbSession *ds, Tcl_Interp *interp)
{
  if (WtOwnTable(&ds->insertions, interp)) {
    return ds->insertions;
  }
  return NULL;
}

/* Get the deletions */

Tcl_Obj *WtGetDbSessionDeletions(WtDbSession *ds, Tcl_Interp *interp)
{
  if (WtOwnTable(&ds->deletions, interp)) {
    return ds->deletions;
  }
  return NULL;
}

/* Is a commit needed */

int WtCommitIsPending(WtDbSession *ds)
{
  return ds->isModified || (ds->isOpen && !ds->syncedAccessTime);
}

/* Commit the session */

int WtCommitDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 1, i = 0;
  time_t now, accessTime = -1;
  Tcl_Obj *objv[2], *tbl = NULL, *keyDeletions, *insertions, *items;
  WtSession *s = WtGetAssocSession(interp);

  if (WtCommitIsPending(ds)) {
    if (!WtOpenDbSessionResource(ds, interp)) {
      ok = 0;
    } else {
      tbl = WtNewTableObj();
      Tcl_IncrRefCount(tbl);

      WtTableSetStrToObj(tbl, "clearAll", WtNewBool(ds->isCleared));
      WtTableSetStrToObj(tbl, "deletions", Tcl_NewListObj(0, NULL));

      if (!(keyDeletions = WtGetDbSessionDeletions(ds, interp))) {
        ok = 0;
      } else {
        WtTableSetStrToObj(tbl, "keyDeletions", keyDeletions);
        if (!(insertions = WtGetDbSessionInsertions(ds, interp))) {
          ok = 0;
        } else {
          WtTableSetStrToObj(tbl, "insertions", insertions); 
          WtTableSetStrToObj(tbl, "isAccessed", WtNewBool(ds->isOpen));
          WtTableSetStrToObj(tbl, "updateAccessTime", WtNewBool(0));

          if (ds->isOpen) {
            now = time(NULL);
            if (s->clientTimeStamp == -1 ||
                now - s->clientTimeStamp > ds->accessTimeInterval) {
              accessTime = now;
              WtTableSetStrToObj(tbl, "updateAccessTime", WtNewBool(1));
            }
          }

          WtTableSetStrToObj(tbl, "isModified",
            WtNewBool(ds->isModified));

          if (ds->isFetched) {
            if (!(items = WtGetDbSessionItems(ds, interp))) {
              ok = 0;
            } else {
              WtTableSetStrToObj(tbl, "items", items);
            }
          }
        }
      }
    }

    if (ok) {
      objv[i] = ds->id;
      objv[++i] = tbl;

      if (WtDbSessionHandlerStr(ds, ds->dbHandler, "update",
          interp, i + 1, objv) != TCL_OK) {
        ok = 0;
      } else {
        if (accessTime != -1) {
          ds->accessTime = accessTime;
        }
        ds->syncedAccessTime = 1;
        WtClearDbSessionUpdates(ds, interp);
      }
    }

    if (tbl) {
      Tcl_DecrRefCount(tbl);
    }

    ds->commitStatus = ok ? WT_STATUS_OK : WT_STATUS_ERROR;
  }

  return ok;
}

/* Delete a session */

int WtDeleteDbSession(WtDbSession *ds, Tcl_Obj *id, Tcl_Interp *interp)
{
  int ok = 1;

  if (id) {
    if (!Tcl_GetCharLength(id)) {
      Tcl_AppendResult(interp, "Session ID string is invalid.", NULL);
      ok = 0;
    }
  } else if (!(id = WtGetDbSessionId(ds, interp))) {
    ok = 0;
  }

  if (ok) {
    if (WtDbSessionHandlerStr(ds, ds->dbHandler, "delete",
        interp, 1, &id) != TCL_OK) {
      ok = 0;
    } else if (ds->id && !strcmp(WtToString(id), WtToString(ds->id))) {
      if (ds->isOpen) {
        ok = WtCloseDbSession(ds, interp, 0);
      }
    }
  }

  return ok;
}

/* Delete stale sessions */

int WtSweepDbSessions(WtDbSession *ds, Tcl_Interp *interp)
{
  WtSession *s = WtGetAssocSession(interp);
  Tcl_Obj *maxIdleTime = Tcl_NewIntObj(s->maxIdleTime);

  if (WtDbSessionHandlerStr(ds, ds->dbHandler, "sweep", interp,
      1, &maxIdleTime) == TCL_OK) {
    return 1;
  }

  return 0;
}

/* Clear the cache */

int WtClearDbSessionCache(WtDbSession *ds, Tcl_Interp *interp)
{
  Tcl_Obj *items;

  if (items = WtGetDbSessionItems(ds, interp)) {
    WtClearTable(items);
    ds->isFetched = 0;
    return 1;
  }

  return 0;
}

/* Clear the updates */

int WtClearDbSessionUpdates(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *deletions, *insertions;

  if (!(deletions = WtGetDbSessionDeletions(ds, interp))) {
    ok = 0;
  } else {
    WtClearTable(deletions);
    if (!(insertions = WtGetDbSessionInsertions(ds, interp))) {
      ok = 0;
    } else {
      WtClearTable(insertions);
      ds->isModified = 0;
      ds->isCleared = 0;
    }
  }

  return ok;
}

/* Unload module */

int WtUnloadDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 1, doCommit;
  Tcl_Obj *err, *msg;

  msg = WtNewString(NULL);
  Tcl_IncrRefCount(msg);

  doCommit = ds->commitStatus != WT_STATUS_ERROR;

  if (ds->isOpen && !WtCloseDbSession(ds, interp, doCommit)) {
    err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
    Tcl_AppendObjToObj(msg, err);
    ok = 0;
  }

  if (ds->dbHandlerLoaded && !WtUnloadDbSessionStore(ds, interp, 0)) {
    err = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
    if (Tcl_GetCharLength(msg)) {
      Tcl_AppendToObj(msg, "\n    and\n", -1);
    }
    Tcl_AppendObjToObj(msg, err);
    ok = 0;
  }

  if (!ok) {
    Tcl_ResetResult(interp);
    Tcl_AddObjErrorInfo(interp, WtToString(msg), -1);
  }

  Tcl_DecrRefCount(msg);

  return ok;
}

/* Finalize request */

int WtDbSessionFinalizeRequest(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 1;
  time_t accessTime;
  WtSession *s;

  if (ds->isOpen) {
    s = WtGetAssocSession(interp);
    if (WtShouldSweepSessions(s, interp)) {
      ok = WtSweepDbSessions(ds, interp);
    }

    if (WtCommitIsPending(ds) && !WtCommitDbSession(ds, interp)) {
      ok = 0;
    }

    if (ok) {
      accessTime = ds->accessTime == -1 ? s->clientTimeStamp :
        ds->accessTime;
      ok = WtSetSessionCookie(s, ds->id, interp, accessTime);
    }
  }

  return ok;
}

/* Session command */

int WtDbSessionCmd(ClientData clientData, Tcl_Interp *interp,
                   int objc, Tcl_Obj *const objv[])
{
  WtSession *s = WtGetAssocSession(interp);
  WtDbSession *ds;

  /* Initialize the db session */

  if (!s->dbSession) {
    s->dbSession = WtNewDbSessionObj();
  }

  ds = WtDbSessionRep(s->dbSession);

  return WtDbSessionCmdHelper(clientData, interp,
    ds, objc, objv);
}

/* Session command helper */

int WtDbSessionCmdHelper(ClientData clientData, Tcl_Interp *interp,
                       WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  char *subCmd;

  if (objc < 2) {
    WtDbSessionCmdUsage(interp, objv[0]);
    return ret;
  }

  subCmd = Tcl_GetString(objv[1]);

  if (!strcmp(subCmd, "id")) {
    ret = WtDbSessionIdCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "count")) {
    ret = WtDbSessionCountCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "has")) {
    ret = WtDbSessionHasCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "get")) {
    ret = WtDbSessionGetCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "set")) {
    ret = WtDbSessionSetCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "setDefault")) {
    ret = WtDbSessionSetDefaultCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "keys")) {
    ret = WtDbSessionKeysCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "values")) {
    ret = WtDbSessionValuesCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "remove")) {
    ret = WtDbSessionRemoveCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "removeList")) {
    ret = WtDbSessionRemoveListCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "array")) {
    ret = WtDbSessionArrayCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "items")) {
    ret = WtDbSessionItemsCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "clear")) {
    ret = WtDbSessionClearCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "isAccessed")) {
    ret = WtDbSessionIsAccessedCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "isModified")) {
    ret = WtDbSessionIsModifiedCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "commit")) {
    ret = WtDbSessionCommitCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "touch")) {
    ret = WtDbSessionTouchCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "clearCache")) {
    ret = WtDbSessionClearCacheCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "status")) {
    ret = WtDbSessionStatusCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "properties")) {
    ret = WtDbSessionPropertiesCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "delete")) {
    ret = WtDbSessionDeleteCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "sweep")) {
    ret = WtDbSessionSweepCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "dbHandlerNamespace")) {
    ret = WtDbSessionHandlerCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "dbHandlerData")) {
    ret = WtDbSessionHandlerDataCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "dbHandlerEval")) {
    ret = WtDbSessionHandlerEvalCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "unload")) {
    ret = WtDbSessionUnloadCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "loadModule")) {
    ret = WtDbSessionLoadModuleCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "unloadModule")) {
    ret = WtDbSessionUnloadModuleCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "finalizeRequest")) {
    ret = WtDbSessionFinalizeCmd(clientData, interp, ds, objc, objv);
  } else {
    WtDbSessionCmdUsage(interp, objv[0]);
  }

  return ret;
}

/* Session command usage */

WtDbSessionCmdUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = Tcl_GetString(cmd);

  Tcl_AppendResult(interp,
    wtBadUsagePrefix2,
    cmdStr, " id\n",
    cmdStr, " count\n",
    cmdStr, " has key\n",
    cmdStr, " get key ?defaultValue?\n",
    cmdStr, " set key value\n",
    cmdStr, " setDefault key defaultValue\n",
    cmdStr, " keys\n",
    cmdStr, " values\n",
    cmdStr, " remove key\n",
    cmdStr, " removeList keys\n",
    cmdStr, " array arrayName\n",
    cmdStr, " items\n",
    cmdStr, " clear\n",
    cmdStr, " isModified\n",
    cmdStr, " commit\n",
    cmdStr, " clearCache\n",
    cmdStr, " status ?id?\n",
    cmdStr, " properties ?id?\n",
    cmdStr, " delete ?id?\n",
    cmdStr, " sweep\n",
    cmdStr, " dbHandler\n",
    cmdStr, " dbHandlerData\n",
    cmdStr, " dbHandlerEval\n",
    cmdStr, " load\n",
    cmdStr, " unload\n",
    cmdStr, " finalizeRequest\n",
    NULL);
}

/* id command */

int WtDbSessionIdCmd(ClientData clientData, Tcl_Interp *interp,
                     WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    return ret;
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, ds->id ? ds->id : WtNewString(NULL));
    ret = TCL_OK;
  } else if (WtSetDbSessionId(ds, objv[2], interp)) {
    Tcl_SetObjResult(interp, ds->id);
    ret = TCL_OK;
  }

  return ret;
}

/* count command */

int WtDbSessionCountCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    return ret;
  } else if (WtFetchDbSession(ds, interp)) {
    Tcl_SetObjResult(interp, Tcl_NewIntObj(WtTableSize(ds->items)));
    ret = TCL_OK;
  }

  return ret;
}

/* has command */

int WtDbSessionHasCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
    return ret;
  } else if (WtFetchDbSession(ds, interp)) {
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
      WtTableHas(ds->items, objv[2])));
    ret = TCL_OK;
  }

  return ret;
}

/* get command */

int WtDbSessionGetCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *obj;

  if (objc != 3 && objc != 4) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key ?defaultValue?", NULL);
    return ret;
  } else if (WtFetchDbSession(ds, interp)) {
    obj = WtTableGet(ds->items, objv[2]);
    if (obj) {
      Tcl_SetObjResult(interp, obj);
      ret = TCL_OK;
    } else if (objc > 3) {
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    } else {
      Tcl_AppendResult(interp,
        "No entry with key \"", Tcl_GetString(objv[2]),
        "\" exists, and no default value was specified.", NULL);
    }
  }

  return ret;
}

/* set command */

int WtDbSessionSetCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *deletions, *insertions, *items;

  if (objc != 4) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key value", NULL);
    return ret;
  } else if (WtOpenDbSession(ds, interp)) {
    /* Log the update */

    if (deletions = WtGetDbSessionDeletions(ds, interp)) {
      WtTableSet(deletions, objv[2], Tcl_NewBooleanObj(1));
      if (insertions = WtGetDbSessionInsertions(ds, interp)) {
        WtTableSet(insertions, objv[2], objv[3]);
        ds->isModified = 1;
        if (items = WtGetDbSessionItems(ds, interp)) {
          WtTableSet(items, objv[2], objv[3]);
          Tcl_SetObjResult(interp, objv[3]);
          ret = TCL_OK;
        }
      }
    }

    ret = TCL_OK;
  }

  return ret;
}

/* setDefault command */

int WtDbSessionSetDefaultCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *obj, *deletions, *insertions, *items;

  if (objc != 4) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key defaultValue",
        NULL);
    return ret;
  } else if (WtFetchDbSession(ds, interp)) {
    obj = WtTableGet(ds->items, objv[2]);
    if (obj) {
      Tcl_SetObjResult(interp, obj);
      ret = TCL_OK;
    } else {
      /* Log the update */

      if (deletions = WtGetDbSessionDeletions(ds, interp)) {
        WtTableSet(deletions, objv[2], Tcl_NewBooleanObj(1));
        if (insertions = WtGetDbSessionInsertions(ds, interp)) {
          WtTableSet(insertions, objv[2], objv[3]);
          ds->isModified = 1;
          if (items = WtGetDbSessionItems(ds, interp)) {
            WtTableSet(items, objv[2], objv[3]);
            Tcl_SetObjResult(interp, objv[3]);
            ret = TCL_OK;
          }
        }
      }
    }
  }

  return ret;
}

/* keys command */

int WtDbSessionKeysCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *keys;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtFetchDbSession(ds, interp)) {
    if (WtTableKeys(ds->items, &keys, interp)) {
      Tcl_SetObjResult(interp, keys);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* values command */

int WtDbSessionValuesCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *vals;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtFetchDbSession(ds, interp)) {
    if (WtTableValues(ds->items, &vals, interp)) {
      Tcl_SetObjResult(interp, vals);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* remove command */

int WtDbSessionRemoveCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *deletions, *insertions, *items;

  if (objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
  } else if (WtOpenDbSession(ds, interp)) {
    /* Log the update */

    if (deletions = WtGetDbSessionDeletions(ds, interp)) {
      WtTableSet(ds->deletions, objv[2], Tcl_NewBooleanObj(1));
      if (insertions = WtGetDbSessionInsertions(ds, interp)) {
        WtTableRemove(insertions, objv[2]);
        ds->isModified = 1;
        /* Update the cache */
        if (items = WtGetDbSessionItems(ds, interp)) {
          WtTableRemove(items, objv[2]);
          ret = TCL_OK;
        }
      }
    }
  }

  return ret;
}

/* removeList command */

int WtDbSessionRemoveListCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, listLen, i;
  Tcl_Obj *key, *deletions, *insertions, *items;

  if (objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
  } else {
    if (Tcl_ListObjLength(interp, objv[2], &listLen) != TCL_OK) {
      Tcl_AppendResult(interp,
        "Argument is not a list. Usage: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
    } else if (WtOpenDbSession(ds, interp)) {
      /* Log the update */

      if ((deletions = WtGetDbSessionDeletions(ds, interp)) &&
          (insertions = WtGetDbSessionInsertions(ds, interp))) {
        ret = TCL_OK;

        for (i = 0; i < listLen; i++) {
          if (Tcl_ListObjIndex(interp, objv[2], i, &key) != TCL_OK) {
            ret = TCL_ERROR;
            break;
          }
          WtTableSet(deletions, key, Tcl_NewBooleanObj(1));
          WtTableRemove(insertions, key);
        }

        if (ret == TCL_OK) {
          ds->isModified = 1;
          /* Update the cache */
          if (!(items = WtGetDbSessionItems(ds, interp)) ||
              !WtTableRemoveList(items, objv[2], interp)) {
            ret = TCL_ERROR;
          }
        }
      }
    }
  }

  return ret;
}

/* array command */

int WtDbSessionArrayCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " arrayName",
      NULL);
  } else if (WtFetchDbSession(ds, interp)) {
    if (WtTableToArray(interp, ds->items, objv[2], 0)) {
      ret = TCL_OK;
    }
  }

  return ret;
}

/* items command */

int WtDbSessionItemsCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtFetchDbSession(ds, interp)) {
    Tcl_SetObjResult(interp, ds->items);
    ret = TCL_OK;
  }

  return ret;
}

/* clear command */

int WtDbSessionClearCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtOpenDbSession(ds, interp)) {
    WtClearDbSessionCache(ds, interp);
    WtClearDbSessionUpdates(ds, interp);
    ds->isCleared = 1;
    ds->isModified = 1;
    ret = TCL_OK;
  }

  return ret;
}

/* isAccessed command */

int WtDbSessionIsAccessedCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(ds->isOpen));
    ret = TCL_OK;
  }

  return ret;
}

/* isModified command */

int WtDbSessionIsModifiedCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(ds->isModified));
    ret = TCL_OK;
  }

  return ret;
}

/* commit command */

int WtDbSessionCommitCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtCommitDbSession(ds, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* touch command */

int WtDbSessionTouchCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtOpenDbSession(ds, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* clearCache command */

int WtDbSessionClearCacheCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    WtClearDbSessionCache(ds, interp);
    ret = TCL_OK;
  }

  return ret;
}

/* status command
 *
 * Result values:
 *
 *     "new" - The session is new and hasn't been saved.
 *     "active" - Session is active.
 *     "expired" - Session has expired.
 */

int WtDbSessionStatusCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, done = 0;
  Tcl_Obj *id;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?", NULL);
  } else {
    if (objc > 2) {
      id = objv[2];
    } else if (id = WtGetDbSessionId(ds, interp)) {
      if (ds->isAutoId) {
        Tcl_SetResult(interp, "new", NULL);
        done = 1;
      }
    }
    if (id && !done) {
      ret = WtDbSessionHandlerStr(ds, ds->dbHandler, "status",
        interp, 1, &id);
    }
  }

  return ret;
}

/* properties command */

int WtDbSessionPropertiesCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, done = 0;
  Tcl_Obj *id, *tbl;
  WtSession *s;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?", NULL);
  } else {
    id = objc > 2 ? objv[2] : WtGetDbSessionId(ds, interp);
    if (id) {
      if (WtDbSessionHandlerStr(ds, ds->dbHandler, "properties",
          interp, 1, &id) == TCL_OK) {
        if (!(tbl = Tcl_GetObjResult(interp)) ||
            !WtOwnTable(&tbl, interp)) {
          Tcl_AppendResult(interp, "Failed to read return value.", NULL);
        } else {
          Tcl_IncrRefCount(tbl);
          if (WtFetchDbSession(ds, interp)) {
            WtTableSetStrToInt(tbl, "size", WtTableSize(ds->items));
            WtTableSetStrToObj(tbl, "id", id);
            s = WtGetAssocSession(interp);
            WtTableSetStrToObj(tbl, "clientValue", s->clientValue);
            WtTableSetStrToObj(tbl, "clientId",
              s->clientId ? s->clientId : WtNewString(NULL));
            WtTableSetStrToLong(tbl, "clientTimeStamp",
              (long)s->clientTimeStamp);
            Tcl_SetObjResult(interp, tbl);
            ret = TCL_OK;
          }
          Tcl_DecrRefCount(tbl);
        }
      }
    }
  }

  return ret;
}


/* delete command */

int WtDbSessionDeleteCmd(ClientData clientData, Tcl_Interp *interp,
                         WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?", NULL);
    return ret;
  } else if (WtDeleteDbSession(ds, NULL, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* sweep command */

int WtDbSessionSweepCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (WtSweepDbSessions(ds, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* dbHandler command */

int WtDbSessionHandlerCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?dbHandler?", NULL);
  } else if (objc == 2) {
    Tcl_SetObjResult(interp, ds->dbHandler);
    ret = TCL_OK;
  } else if (objc == 3) {
    if (WtSetDbSessionHandler(ds, objv[2], interp)) {
      Tcl_SetObjResult(interp, ds->dbHandler);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* dbHandlerData command */

int WtDbSessionHandlerDataCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?dbHandlerData?", NULL);
  } else {
    if (objc == 2) {
      Tcl_SetObjResult(interp, ds->dbHandlerData);
    } else if (objc == 3) {
      Tcl_DecrRefCount(ds->dbHandlerData);
      ds->dbHandlerData = objv[2];
      Tcl_IncrRefCount(ds->dbHandlerData);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* dbHandlerEval command */

int WtDbSessionHandlerEvalCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc < 3) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " method ?parameter? ...", NULL);
  } else {
    ret = WtDbSessionHandlerStr(ds, ds->dbHandler, Tcl_GetString(objv[2]),
      interp, objc - 3, objv + 3);
  }

  return ret;
}

/* unload command */

int WtDbSessionUnloadCmd(ClientData clientData, Tcl_Interp *interp,
                         WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else if (WtUnloadDbSession(ds, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* load command */

int WtDbSessionLoadModuleCmd(ClientData clientData, Tcl_Interp *interp,
                             WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    /* Do nothing */
    ret = TCL_OK;
  }

  return ret;
}

/* unloadModule command */

int WtDbSessionUnloadModuleCmd(ClientData clientData, Tcl_Interp *interp,
                               WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  WtContext *w;
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    if (WtUnloadDbSession(ds, interp)) {
      ret = TCL_OK;
    }
    w = WtGetAssocContext(interp);
    Tcl_DecrRefCount(w->web->session.dbSession);
    w->web->session.dbSession = NULL;
  }

  return ret;
}

/* finalizeRequest command */

int WtDbSessionFinalizeCmd(ClientData clientData, Tcl_Interp *interp,
                           WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    return ret;
  } else if (WtDbSessionFinalizeRequest(ds, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* Invoke DB handler */

int WtDbSessionHandler(WtDbSession *ds, Tcl_Obj *handler,
    Tcl_Obj *method, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
  return WtDbSessionHandlerInternal(ds, handler, method,
    interp, objc, objv);
}

/* Using a C string */

int WtDbSessionHandlerStr(WtDbSession *ds, Tcl_Obj *handler,
    const char *methodStr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
  int ret;
  Tcl_Obj *method = WtNewString(methodStr);
  Tcl_IncrRefCount(method);
  ret = WtDbSessionHandlerInternal(ds, handler, method,
    interp, objc, objv);
  Tcl_DecrRefCount(method);
  return ret;
}

/* All options */

int WtDbSessionHandlerInternal(WtDbSession *ds,
    Tcl_Obj *handler, Tcl_Obj *method, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, i, len, n, addScope = 1;
  Tcl_Obj **args, *cmd, *err, *res;
  const char *handlerStr;

  if (WtPrepareDbSessionHandler(ds, method, interp)) {

    /* Build the command */

    cmd = Tcl_NewStringObj(NULL, 0);
    Tcl_IncrRefCount(cmd);
    Tcl_AppendObjToObj(cmd, ds->dbHandler);

    len = Tcl_GetCharLength(ds->dbHandler);
    if (len >= 2) {
      handlerStr = WtToString(ds->dbHandler);
      if (handlerStr[len - 1] == ':' && handlerStr[len - 2] == ':') {
        addScope = 0;
      }
    }

    if (addScope) {
      Tcl_AppendToObj(cmd, "::", 2);
    }

    Tcl_AppendObjToObj(cmd, method);

    n = sizeof(Tcl_Obj *) * (objc + 1);
    args = (Tcl_Obj **)ckalloc(n);
    args[0] = cmd;
    n = objc * sizeof(Tcl_Obj *);
    memcpy(args + 1, objv, n);

    for (i = 0; i < objc + 1; i++) {
      Tcl_IncrRefCount(args[i]);
    }

    /* Evaluate the command */

    WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, WtGetContext(),
      "WtDbSessionHandlerInternal: Calling: \"%s\".",
      WtToString(cmd));

    ret = Tcl_EvalObjv(interp, objc + 1, args, TCL_EVAL_DIRECT);

    err = Tcl_GetVar2Ex(interp, "errorInfo", NULL,
     TCL_GLOBAL_ONLY);
    res = Tcl_GetObjResult(interp);

    for (i = 0; i < objc + 1; i++) {
      Tcl_DecrRefCount(args[i]);
    }

    ckfree((char *)args);
    Tcl_DecrRefCount(cmd);
  }

  return ret;
}

/* Prepare the db handler before passing along any commands */

int WtPrepareDbSessionHandler(WtDbSession *ds, Tcl_Obj *method, Tcl_Interp *interp)
{
  int ok = 1;
  const char *methodStr = WtToString(method);

  if (!Tcl_GetCharLength(ds->dbHandler)) {
    Tcl_AppendResult(interp, "No database handler namespace defined.",
      NULL);
    ok = 0;
  } else {
    /* Load the db handler */

    if (!ds->dbHandlerLoaded && strcmp(methodStr, "load") &&
        strcmp(methodStr, "unload")) {
      ok = WtLoadDbSessionStore(ds, interp);
    }
  }

  return ok;
}

/* Create session commands */

void WtInitDbSessionCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "::wt::session::dbSession::dbSessionHandler",
    WtDbSessionCmd, NULL, NULL);
}
